---
sidebar_position: 5
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

# Testing

This document provides a brief overview on how to set up unit tests with `jest` when using the `@rx-angular/state`
package.

:::info
Make sure you are familiar with the basics of how to test Angular applications, you can read more about it in the [official testing docs](https://angular.io/guide/testing).
It is also recommended to read the [official component testing docs](https://angular.io/guide/testing-components-basics).
:::

## Basic Testing

There are two ways you can test your state. Depending on your use case, you maybe want to test the behavior of a whole component, or test the state and it's transformations on its own.

:::tip

- Note that you want your tests to be unrelated to implementation details as much as possible.
- Keep in mind that testing a component with DOM nodes instead of component instance is considered as a best practice.

:::

<Tabs>

<TabItem value="component" label="counter.component.ts">

```typescript title="/src/counter.component.ts"
import { rxState } from '@rx-angular/state';
import { Component } from '@angular/core';
import { Subject, map, merge } from 'rxjs';
import { AsyncPipe } from '@angular/common';

@Component({
  selector: 'rx-counter',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    <button id="increment" (click)="increment.next()">Increment</button>
    <button id="decrement" (click)="decrement.next()">Decrement</button>
    <div id="counter">{{ count$ | async }}</div>
  `,
})
export class CounterComponent {
  readonly increment = new Subject<void>();
  readonly decrement = new Subject<void>();

  readonly state = rxState<{ count: number }>(({ connect, set }) => {
    set({ count: 0 });
    connect(
      'count',
      merge(
        this.increment.pipe(map(() => 1)),
        this.decrement.pipe(map(() => -1))
      ),
      ({ count }, slice) => count + slice
    );
  });

  readonly count$ = this.state.select('count');
}
```

</TabItem>

<TabItem value="spec" label="counter.component.spec.ts">

```typescript title="/src/counter.component.spec.ts"
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CounterComponent } from './counter.component';

describe('CounterComponent', () => {
  let fixture: ComponentFixture<CounterComponent>;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [CounterComponent],
    });
    fixture = TestBed.createComponent(CounterComponent);
  });

  it('should increment', () => {
    const incrementButton = fixture.debugElement.query(By.css('#increment'));
    incrementButton.triggerEventHandler('click', null);
    fixture.detectChanges();
    expect(
      fixture.debugElement.query(By.css('#counter')).nativeElement.textContent
    ).toBe('1');
  });

  it('should decrement', () => {
    const decrementButton = fixture.debugElement.query(By.css('#decrement'));
    decrementButton.triggerEventHandler('click', null);
    fixture.detectChanges();
    expect(
      fixture.debugElement.query(By.css('#counter')).nativeElement.textContent
    ).toBe('-1');
  });
});
```

</TabItem>

</Tabs>

## Testing State with Marble Diagrams

If you want to test the state and its transformations on its own, you can use the `TestScheduler` from `rxjs/testing` to test your state with marble diagrams.

<Tabs>

<TabItem value="state" label="counter.state.ts">

```typescript title="/src/counter.state.ts"
import { rxState } from '@rx-angular/state';
import { Injectable } from '@angular/core';
import { Subject, map, merge } from 'rxjs';

@Injectable()
export class CounterService {
  readonly increment = new Subject<void>();
  readonly decrement = new Subject<void>();

  private readonly state = rxState<{ count: number }>(({ connect, set }) => {
    set({ count: 0 });
    connect(
      'count',
      merge(
        this.increment.pipe(map(() => 1)),
        this.decrement.pipe(map(() => -1))
      ),
      ({ count }, slice) => count + slice
    );
  });

  readonly count$ = this.state.select('count');
}
```

</TabItem>

<TabItem value="spec" label="counter.state.spec.ts">

```typescript title="/src/counter.state.spec.ts"
import { TestScheduler } from 'rxjs/testing';
import { TestBed } from '@angular/core/testing';
import { CounterService } from './counter.service';

const testScheduler = new TestScheduler((actual, expected) => {
  expect(actual).toEqual(expected);
});

describe('CounterService', () => {
  let service: CounterService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [CounterService],
    });
    service = TestBed.inject(CounterService);
  });

  it('should increment', () => {
    testScheduler.run(({ expectObservable }) => {
      service.increment.next();
      expectObservable(service.count$).toBe('a', { a: 1 });
    });
  });

  it('should decrement', () => {
    testScheduler.run(({ expectObservable }) => {
      service.decrement.next();
      expectObservable(service.count$).toBe('a', { a: -1 });
    });
  });
});
```

</TabItem>

</Tabs>
